package dxn.nl.export.deal.netty;

import com.alibaba.fastjson2.JSON;
import dxn.boot.common.config.CsApplicationContext;
import dxn.nl.export.deal.export.ExportDealSteamQueryBiz;
import dxn.nl.export.deal.export.ExportSessionManager;
import dxn.nl.file.biz.ExportStatusEnum;
import dxn.nl.file.biz.po.FileExportApplyDetail;
import dxn.nl.file.biz.service.ExportFileBaseService;
import dxn.nl.file.client.model.dto.CsExportDealTypeHeadDto;
import dxn.nl.file.client.model.dto.TransferMessageDto;
import dxn.nl.file.client.model.enums.ExportDealResultTypeEnum;
import dxn.nl.file.client.model.enums.ExportFileTypeEnum;
import dxn.nl.file.client.model.enums.ExportMessageTypeEnum;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.Callable;


public class ExportDealHandler extends SimpleChannelInboundHandler<TransferMessageDto> {
    private static final Logger log = LoggerFactory.getLogger(ExportDealHandler.class);

    private final static EventExecutorGroup group = new DefaultEventExecutorGroup(400);

    @Override
    protected void channelRead0(ChannelHandlerContext context, TransferMessageDto obj) {
        SocketChannel socketChannel = (SocketChannel) context.channel();
        group.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                dealData(socketChannel,obj);
                return null;
            }
        });

        ReferenceCountUtil.release(obj);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info(">>>>>>>>连接");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        SocketChannel socketChannel = (SocketChannel) ctx.channel();
        log.info(socketChannel.remoteAddress()+">>>>>>>>退出");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("",cause);
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        if (evt instanceof IdleStateEvent event) {
            if (event.state().equals(IdleState.READER_IDLE)) {
                System.out.println("长期没收到服务器推送数据");
                //可以选择重新连接
            } else if (event.state().equals(IdleState.WRITER_IDLE)) {
                System.out.println("长期未向服务器发送数据");
                //发送心跳包
                TransferMessageDto csNioMessage = new TransferMessageDto();
                csNioMessage.setType(ExportMessageTypeEnum.heartbeat.code);
                ctx.writeAndFlush(csNioMessage);
            } else if (event.state().equals(IdleState.ALL_IDLE)) {
                System.out.println("ALL");
            }
        }
    }

    private void dealData(SocketChannel socketChannel,TransferMessageDto message)
    {
        if(message == null)
        {
            return;
        }

        try{
            if(message.getType() == ExportMessageTypeEnum.heartbeat.code)
            {
                // 心跳包，直接结束
                return ;
            }
            log.info("收到消息：{} {} {}",message.getType(),message.getConfig().getModeCode(),message.getConfig().getId());
            message.getConfig().setServer(SocketManager.appSessionId);

            if(ExportMessageTypeEnum.deal_result.code == message.getType())
            {
                // 处理结果 直接触发即可
                SocketManager.saveDataToFuture(message);
                return ;
            }

            if(ExportMessageTypeEnum.send_data.code == message.getType())
            {
                if(message.getConfig().getFileType() != ExportFileTypeEnum.export.code)
                {
                    sendFailBack(socketChannel,message,ExportMessageTypeEnum.deal_result.code,"只有导出才能发送数据");
                }
                String error = ExportSessionManager.dealResponseData(message);
                if(message.getConfig().getDealResult() == ExportDealResultTypeEnum.continue_deal.code)
                {
                    // 如果是继续发送，就返回本次处理的结果，否则结束了，不需要再给客户端发送数据了
                    if(StringUtils.isBlank(error))
                    {
                        sendBack(socketChannel,message,ExportMessageTypeEnum.deal_result.code,ExportDealResultTypeEnum.continue_deal.code);
                    }
                    else
                    {
                        sendFailBack(socketChannel,message,ExportMessageTypeEnum.deal_result.code,error);
                    }
                }
                return ;
            }

            if(message.getType() == ExportMessageTypeEnum.create_export.code)
            {
                createApplyExport(socketChannel,message);
                return ;
            }

            sendFailBack(socketChannel,message,ExportMessageTypeEnum.deal_result.code,"数据类型不正确");
            log.info("数据类型不正确");
        }
        catch (Exception ex)
        {
            log.error("",ex);
        }
    }

    private void createApplyExport(SocketChannel socketChannel,TransferMessageDto message)
    {
        ExportFileBaseService exportFileBaseService = CsApplicationContext.getBean(ExportFileBaseService.class);

        FileExportApplyDetail fileExportApplyDetail = new FileExportApplyDetail();
        fileExportApplyDetail.setId(System.currentTimeMillis());
        fileExportApplyDetail.setAppId(message.getConfig().getAppId());
        fileExportApplyDetail.setModeCode(message.getConfig().getModeCode());
        fileExportApplyDetail.setModeName(message.getConfig().getModeName());
        fileExportApplyDetail.setOrgCode(message.getMessageData().getOrgCode());
        fileExportApplyDetail.setOrgName(message.getMessageData().getOrgName());
        fileExportApplyDetail.setApplyUserCode(message.getMessageData().getUserCode());
        fileExportApplyDetail.setApplyUserName(message.getMessageData().getUserName());
        fileExportApplyDetail.setApplyTime(LocalDateTime.now());
        fileExportApplyDetail.setStatus(ExportStatusEnum.start_deal.code);
        fileExportApplyDetail.setExportTypeCode(1);
        fileExportApplyDetail.setVersionNo(0L);
        fileExportApplyDetail.setStartTime(LocalDateTime.now());
        exportFileBaseService.save(fileExportApplyDetail);

        CsExportDealTypeHeadDto dto = null;
        if(message.getData() != null || message.getData().length > 0)
        {
            dto = JSON.parseObject(message.getData(),CsExportDealTypeHeadDto.class);
        }
        if(dto == null || CollectionUtils.isEmpty(dto.getSheetHeadMap()))
        {
            TransferMessageDto backPo = message.convertNewResultMessage(ExportMessageTypeEnum.deal_result.code
                    , ExportDealResultTypeEnum.fail.code,"excel头信息为空");
            socketChannel.writeAndFlush(backPo);
            fileExportApplyDetail.setMessage("头信息为空");
            exportFileBaseService.updateStatus(fileExportApplyDetail.getId(),ExportStatusEnum.deal_exception,0,"头信息为空");
            return ;
        }


        // dao map,
        ExportDealSteamQueryBiz steamQueryBiz = new ExportDealSteamQueryBiz(dto.getSheetHeadMap(),exportFileBaseService, fileExportApplyDetail);
        String sessionId = UUID.randomUUID().toString();
        message.getConfig().setSessionId(sessionId);
        ExportSessionManager.addExportUtil(sessionId,steamQueryBiz);

        TransferMessageDto backPo = message.convertNewResultMessage(ExportMessageTypeEnum.deal_result.code
                , ExportDealResultTypeEnum.continue_deal.code);
        socketChannel.writeAndFlush(backPo);
    }

    private void sendBack(SocketChannel socketChannel,TransferMessageDto messagePo,int dealType,int dealResult)
    {
        TransferMessageDto backPo = messagePo.convertNewResultMessage(dealType, dealResult);
        socketChannel.writeAndFlush(backPo);
    }

    private void sendFailBack(SocketChannel socketChannel,TransferMessageDto messagePo,int dealType,String message)
    {
        TransferMessageDto backPo = messagePo.convertNewResultMessage(dealType
                , ExportDealResultTypeEnum.fail.code,message);
        socketChannel.writeAndFlush(backPo);
    }
}